#include "http_data.hh"
#include "../../src/repo/src/include/root/coroutine/coroutine.h"
#include "../box/box_channel.hh"
#include "../detail/box_alloc.hh"
#include "../detail/http_base_impl.hh"
#include <sstream>

kratos::http::HttpCallImpl::HttpCallImpl() {}

kratos::http::HttpCallImpl::~HttpCallImpl() {
  get_base()->close_channel(channel_id_);
}

auto kratos::http::HttpCallImpl::finish_parse() -> void {
  status_code_ = static_cast<int>(parser_->status_code);
  method_ = http_method_str(static_cast<http_method>(parser_->method));
}

auto kratos::http::HttpCallImpl::set_headers(const HeaderMap &headers) -> void {
  header_map_ = headers;
}

auto kratos::http::HttpCallImpl::set_base(HttpBaseImpl *base) -> void {
  base_ = base;
}

auto kratos::http::HttpCallImpl::get_base() -> HttpBaseImpl * { return base_; }

auto kratos::http::HttpCallImpl::set_channel_id(std::uint64_t channel_id)
    -> void {
  channel_id_ = channel_id;
}

auto kratos::http::HttpCallImpl::get_channel_id() -> std::uint64_t {
  return channel_id_;
}

auto kratos::http::HttpCallImpl::set_host(const std::string &host) -> void {
  host_ = host;
}

auto kratos::http::HttpCallImpl::get_host() -> const std::string & {
  return host_;
}

auto kratos::http::HttpCallImpl::set_port(int port) -> void { port_ = port; }

auto kratos::http::HttpCallImpl::get_port() -> int { return port_; }

auto kratos::http::HttpCallImpl::is_keep_alive() -> bool {
  return (http_should_keep_alive(parser_.get()) ? true : false);
}

auto kratos::http::HttpCallImpl::get_parser() -> http_parser * {
  return parser_.get();
}

auto kratos::http::HttpCallImpl::get_settings() -> http_parser_settings * {
  return settings_.get();
}

auto kratos::http::HttpCallImpl::get_buffer() -> const char * {
  return buffer_.get();
}

auto kratos::http::HttpCallImpl::get_resized_buffer(std::size_t recved_bytes)
    -> char * {
  if (!buffer_length_) {
    buffer_ = kratos::make_unique_pool_ptr<char>(max_buffer_length_);
  }
  if (recved_bytes + buffer_length_ > max_buffer_length_) {
    auto new_max_size = recved_bytes + buffer_length_ + max_buffer_length_;
    auto new_buffer = kratos::make_unique_pool_ptr<char>(new_max_size);
    memcpy(new_buffer.get(), buffer_.get(), buffer_length_);
    max_buffer_length_ = new_max_size;
    buffer_.swap(new_buffer);
  }
  return buffer_.get();
}

auto kratos::http::HttpCallImpl::adjust_buffer_length(std::size_t recved_bytes)
    -> void {
  buffer_length_ += recved_bytes;
}

auto kratos::http::HttpCallImpl::get_buffer_len() -> std::size_t {
  return buffer_length_;
}

auto kratos::http::HttpCallImpl::set_body_info(const char *body_at,
                                               std::size_t body_len) -> void {
  body_start_ = body_at - buffer_.get();
  body_length_ = body_len;
}

auto kratos::http::HttpCallImpl::get_headers() -> const HeaderMap & {
  return header_map_;
}

auto kratos::http::HttpCallImpl::add_header(const std::string &key,
                                            const std::string &value) -> void {
  header_map_[key] = value;
}

auto kratos::http::HttpCallImpl::add_header_key(const std::string &key)
    -> void {
  last_header_key_ = key;
}

auto kratos::http::HttpCallImpl::add_header_value(const std::string &value)
    -> void {
  header_map_[last_header_key_] = value;
}

auto kratos::http::HttpCallImpl::get_content() -> std::string {
  return std::string(buffer_.get() + body_start_, body_length_);
}

auto kratos::http::HttpCallImpl::set_content(const std::string &content)
    -> void {
  add_header("Content-Length", std::to_string(content.size()));
  body_ = content;
}

auto kratos::http::HttpCallImpl::get_full_content() -> std::string {
  return std::string(buffer_.get(), buffer_length_);
}

auto kratos::http::HttpCallImpl::get_method() -> const std::string & {
  return method_;
}

auto kratos::http::HttpCallImpl::set_method(const std::string &method) -> void {
  method_ = method;
}

auto kratos::http::HttpCallImpl::set_status_code(int code) -> void {
  status_code_ = code;
}

auto kratos::http::HttpCallImpl::get_status_code() -> int {
  return status_code_;
}

auto kratos::http::HttpCallImpl::get_uri() -> std::string { return uri_; }

auto kratos::http::HttpCallImpl::set_uri(const std::string &url) -> void {
  uri_ = url;
}

auto kratos::http::HttpCallImpl::get_response() -> HttpCallPtr {
  return HttpCallPtr();
}

auto kratos::http::HttpCallImpl::close() -> void {
  get_base()->close_channel(channel_id_);
}

auto kratos::http::HttpCallImpl::run_coro() -> void {
  coro_id_ = coro_id();
  coro_yield();
}

auto kratos::http::HttpCallImpl::wakeup() -> void {
  if (coro_id_) {
    coro_resume(coro_id_);
  }
}

auto kratos::http::HttpCallImpl::is_coro() -> bool { return (coro_id_ != 0); }

auto kratos::http::HttpCallImpl::set_msg_complete() -> void {
  msg_complete_ = true;
}

auto kratos::http::HttpCallImpl::is_msg_complete() -> bool {
  return msg_complete_;
}

auto kratos::http::HttpCallImpl::set_user_data(std::uint64_t user_data)
    -> void {
  user_data_ = user_data;
}

auto kratos::http::HttpCallImpl::get_user_data() -> std::uint64_t {
  return user_data_;
}

kratos::http::HttpRequest::HttpRequest() {
  parser_ = kratos::make_unique_pool_ptr<http_parser>();
  http_parser_init(parser_.get(), HTTP_RESPONSE);
  settings_ = kratos::make_unique_pool_ptr<http_parser_settings>();
  http_parser_settings_init(settings_.get());
  parser_->data = this;
}

auto kratos::http::HttpRequest::set_handler(ResponseHandler handler) -> void {
  handler_ = handler;
}

auto kratos::http::HttpRequest::get_handler() -> ResponseHandler {
  return handler_;
}

auto kratos::http::HttpCallImpl::send_request() -> void {
  std::stringstream ss;
  ss << get_method() << " " << get_uri() << " HTTP/1.1\r\n";
  ss << "Host: " << get_host() << ":" << get_port() << "\r\n";
  for (const auto &header : get_headers()) {
    ss << header.first << ": " << header.second << "\r\n";
  }
  ss << "\r\n";
  if (!body_.empty()) {
    ss << body_;
  }
  auto channel = get_base()->get_channel(get_channel_id());
  if (channel) {
    auto str = ss.str();
    channel->send(str.c_str(), (int)str.size());
  }
}

auto kratos::http::HttpRequest::finish() -> void {
  if (!is_keep_alive()) {
    close();
  }
}

kratos::http::HttpResponse::HttpResponse() {
  parser_ = kratos::make_unique_pool_ptr<http_parser>();
  http_parser_init(parser_.get(), HTTP_REQUEST);
  settings_ = kratos::make_unique_pool_ptr<http_parser_settings>();
  http_parser_settings_init(settings_.get());
  parser_->data = this;
}

auto kratos::http::HttpResponse::set_handler(RequestHandler handler) -> void {
  handler_ = handler;
}

auto kratos::http::HttpResponse::get_handler() -> RequestHandler {
  return handler_;
}

auto kratos::http::HttpCallImpl::send_response() -> void {

  std::stringstream ss;
  ss << "HTTP/1.1 " << get_status_code() << " "
     << http_status_str((http_status)get_status_code()) << "\r\n";
  for (const auto &header : get_headers()) {
    ss << header.first << ": " << header.second << "\r\n";
  }
  ss << "\r\n";
  if (!body_.empty()) {
    ss << body_;
  }
  auto channel = get_base()->get_channel(get_channel_id());
  if (channel) {
    auto str = ss.str();
    channel->send(str.c_str(), (int)str.size());
  }
}

auto kratos::http::HttpResponse::finish() -> void {
  if (!is_keep_alive()) {
    user_response_->add_header("Connection", "close");
  }
  user_response_->send_response();
}

auto kratos::http::HttpResponse::get_response() -> HttpCallPtr {
  if (user_response_) {
    return user_response_;
  }
  user_response_ = kratos::make_unique_pool_ptr<HttpResponse>();
  user_response_->set_base(get_base());
  user_response_->set_channel_id(get_channel_id());
  return user_response_;
}
